/*
TODO: Finish implementation of 'String.prototype.measureString()' method.
String.prototype.measureString = function()
{
	//ruler must have the same font settings as the container which will eventually display the string.
    var ruler = "<span id=\"ruler\" style='font-family:Arial;font-size:50px;'></span>".toHtmlElement();
    application.appendChild(ruler)
    ruler.innerHTML = this;
    return {width:ruler.offsetWidth, height:ruler.offsetHeight}
};

"Hello".measureString();



String.prototype.trimToPx = function(length)
{
    var tmp = this;
    var trimmed = this;
    if (tmp.measureString() > length)
    {
        trimmed += "...";
        while (trimmed.measureString() > length)
        {
            tmp = tmp.substring(0, tmp.length-1);
            trimmed = tmp + "...";
        }
    }

    return trimmed;
}*/

String.prototype.stripHTML = function() {
	var matchTag = /<(?:.|\s)*?>/g;
	return this.replace(matchTag, "");
};

Array.prototype.clean = function(deleteValue) {
  for (var i = 0; i < this.length; i++) {
    if (this[i] == deleteValue) {         
      this.splice(i, 1);
      i--;
    }
  }
  return this;
};

//see http://ejohn.org/blog/javascript-array-remove/
Array.prototype.removeByIndex = function(from, to) {
  var rest = this.slice((to || from) + 1 || this.length);
  this.length = from < 0 ? this.length + from : from;
  return this.push.apply(this, rest);
};

if (!String.prototype.capitalize) {
	String.prototype.capitalize = function(){
		return this.replace(/\w+/g, function(a){
			return a.charAt(0).toUpperCase() + a.substr(1).toLowerCase();
		});
	};
};

Object.isElement = function(object){
    return !!(object && object.nodeType == 1);
};

String.prototype.toHtmlElement = function(){
    var _root = document.createElement('div');
        _root.style.display = "none";
    	_root.innerHTML = this;
		//var el = $(_root).getFirst();
		var el = Sly.search(">*", _root)[0];
    return el;
};


/*
 * Returns the object by it's namespace
 * Example:
 * "com::aol::TabControl".ref() will resolve to the 
 *  object store at com.aol.TabControl
 * 
 */
String.prototype.ref = function(){
    var ref = this.replace(/\:{2}/ig,".");
    return eval(ref);
};



/*
 * 
 * Debouncing ensures that exactly one signal is sent for an event that 
 * may be happening several times � or even several hundreds of times 
 * over an extended period. As long as the events are occurring fast 
 * enough to happen at least once in every detection period, the 
 * signal will not be sent!
 * 
 * see: 
 * http://unscriptable.com/index.php/2009/03/20/debouncing-javascript-methods/
 */
Function.prototype.debounce = function(threshold, execAsap) {
    var func = this, timeout;
    return function debounced() {
        var obj = this, args = arguments;
        function delayed() {
            if (!execAsap) {
                func.apply(obj, args);}
            timeout = null;
        };
        if (timeout) {
            clearTimeout(timeout);}
        else {
            if (execAsap) {func.apply(obj, args);}
        }
        timeout = setTimeout(delayed, threshold || 100);
    }
};

toArray = (function(s){return function(o){return s.call(o,0)}})([].slice);

//extend obj with proto
$C = function(obj, proto){
   for(var key in proto) {
       if (!obj[key]) {
	   	obj[key] = proto[key];
	   }
   };
   return obj;
};

function $CAST(obj, Class, args){
    if (!obj) {
        return false
    }
    if (Class) {
        if (obj.prototype && (obj.prototype instanceof Class)) {
            //console.log("returning proto");
            return obj.prototype;
        }
        else {
            //console.log("initializing Class");
            return new Class((args ||{}), obj);
        }
    }
    else {
        if (Object.isElement(obj)) {
            if (obj.prototype && (obj.prototype instanceof w3c.Element)) {
                //console.log("returning proto");
                return obj.prototype
            }
            else {
                //console.log("initializing Element");
                return new w3c.Element((args ||{}), obj);
            }
        }
        else {
            return obj;
        }
    }
};


Function.prototype.bind = function(context) {
   var thisFunc = this,
       leftArgs = toArray(arguments).slice(1); 
   return function() {
	   try {
	       var args = leftArgs.concat(toArray(arguments).slice(0));
	       return thisFunc.apply(context, args);
	   } 
	   catch (e) {
	   		if(typeof logger != "undefined") {
				logger.error([e.name, e.message, ("Line: " + e.lineNumber), e.stack].join("\n"));
				alert([e.name, e.message, ("Line: " + e.lineNumber), e.stack].join("\n"))
			}
	   }
   };
 };

 
//'x y -> this.name + x'.call({name:"jay"},3,[1,2]);
String.prototype.call = function(){
    if(!Array.prototype.clean) {
        Array.prototype.clean = function(){
		    var results = [];
		    for(var i=0; i<=this.length; i++) {
		        if (!this[i]) {
					delete this[i];
				}
				else {
					results.push(this[i]);
				}
		    }
		    return results;
		};
    };
    var context = arguments[0];
    var args = Array.prototype.slice.call(arguments,1,arguments.length);
    var parts  = this.split("->");
    
    var body = parts[1]||"";
        body = "return " + body;

    var params = parts[0] ? parts[0].replace(/^\s+|\s+$/g,'').split(" "):"";
        params = (params instanceof Array)? params.clean():params;
    
    var f = new Function(params, body);
   
    if(args.length > 0) {
        return f.apply(context||window,args)} 
    else {return f;}
};

window.expression = function(funcstring) {
    if(funcstring.indexOf("->") >= 0) { return funcstring.call();}
    else { return ("-> " + funcstring).call();}
};


/*namespace("js.merge", function (obj1, obj2) {
  if (!obj2) {
  	return obj1;
  };
  for (var p in obj2) {
    try {
      // Property in destination object set; update its value.
      if ( obj2[p].constructor==Object ) {
        obj1[p] = MergeRecursive(obj1[p], obj2[p]);
      } else {
        obj1[p] = obj2[p];
      }
    } catch(e) {
      // Property in destination object not set; create it and set its value.
      obj1[p] = obj2[p];

    }
  }
  return obj1;
});*/
    
    

(function() {
	
	this.ObjUtil = { };
	
	// Methods to determine the type of an object.
	ObjUtil.isObject = function(obj) { return Object.prototype.toString.call(obj) === "[object Object]"; };
	ObjUtil.isString = function(obj) { return Object.prototype.toString.call(obj) === "[object String]"; };
	ObjUtil.isNumber = function(obj) { return Object.prototype.toString.call(obj) === "[object Number]"; };
	ObjUtil.isBoolean = function(obj) { return Object.prototype.toString.call(obj) === "[object Boolean]"; };
	ObjUtil.isArray = function(obj) { return Object.prototype.toString.call(obj) === "[object Array]"; };
	ObjUtil.isFunction = function(obj) { return Object.prototype.toString.call(obj) === "[object Function]"; };
	ObjUtil.isDate = function(obj) { return Object.prototype.toString.call(obj) === "[object Date]"; };
	ObjUtil.isRegExp = function(obj) { return Object.prototype.toString.call(obj) === "[object RegExp]"; };
	
	// Allows for creating method overloads, beware of a slight overhead during method call.  
	// Also note that the only differentiator between functions is their numer of arguments.
	// Thanks to John Resig for the inspiration for this method.
	ObjUtil.addOverload = function(obj, name, func) {
	    var old = obj[name];
	    obj[name] = function() {
	        if (func.length == arguments.length) {
	            return func.apply(obj, arguments);
	        } else if (typeof old == "function") {
	            return old.apply(obj, arguments);
			}
	    };
	};
	
	// A generic comparison operator, to maintain lexicographical order strings are cast 
	// to lower case.
	ObjUtil.compareTo = function(a, b) {
		if (ObjUtil.isString(a)) { a = a.toLowerCase(); }
		if (ObjUtil.isString(b)) { b = b.toLowerCase(); }
		if (a < b) { return -1; }
		if (a > b) { return 1; }
		return 0;
	};
	
	// A generic memoizer function to help with heavy recursion.
	// Ex: var fibonacci = ObjUtil.memo(function (recur, n) { 
		// return recur(n - 1) + recur(n - 2); }, {"0":0, "1":1});
	ObjUtil.memo = function(func, seed) {
	    var cache = seed || {};
	    var shell = function(arg) {
	        if (!(arg in cache)) {
	            cache[arg] = func(shell, arg);
			}
	        return cache[arg];
	    };
	    return shell;
	};
	
	// Gives us partial functions with currying.  Inspired by Oliver Steele's Functional.js library.
	// Parameters to be supplied by the curried function use 'undefined' as a placeholder in the
	// defining function.  Ex: var test = someFunction(a, undefined, c) { }; test(b);
	if (!Function.partial) {
		Function.prototype.partial = function() {
			var fn = this;
			var args = Array.prototype.slice.call(arguments);
	    	return function() {
				var arg = 0;
				for (var i = 0; i < args.length && arg < arguments.length; i++) {
					if (args[i] === undefined) {
						args[i] = arguments[arg++];
					}
				}
				return fn.apply(this, args);
	    	};
		};
	};

	// Converts strings to boolean values
	if (!String.toBoolean) {
		String.prototype.toBoolean = function() { 
			return (this.toLowerCase() == "true" || this == "1");
		};
	};
	
	// Replaces all instance of 'val' within a string and returns a new string.  The native 
	// String.replace method only replaces the first instance of the match.  Unfortunately 
	// you cannot assign to 'this' within a prototype so we have to return a new instance 
	// of the string.
	if (!String.replaceAll) {
		String.prototype.getReplaced = function(val, rep) {
			var s = this.replace(new RegExp(val, "g"), rep);
			return s;
		};
	};
	
	// Removes white space from the beginning and end of a string
	if (!String.trim) {
		String.prototype.trim = function () {
		    return this.replace(/^\s*/, "").replace(/\s*$/, "");
		};
	};
	
	// Copies all items within an array to a new array and returns the new array.  
	// If the array contains comlex types then the new array will contain references 
	// to the old array.
	if (!Array.copy) {
		Array.prototype.copy = function() {
			var c = [];
			this.forEach(function(i) { c.push(i); });
			return c;
		};
	};

	// Wraps a for loop around an array and passes each item in the loop to the 
	// supplied function and calls it.
	if (!Array.forEach) {
		Array.prototype.forEach = function(func) { 
			for (var i in this) { if (this.hasOwnProperty(i)) { func(this[i]); } } 
		};
	};
	
	// Returns the object matching the criteria function.  Note that the 'func' parameter 
	// here must be a function that returns a boolean.
	if (!Array.find) {
		Array.prototype.find = function(func) {
			for (var i in this) { if (func(this[i])) { return this[i]; } }
			return null;
		};
	};

	// Returns an array of objects matching the criteria function.  Note that the 'func' 
	// parameter here must be a function that returns a boolean.
	if (!Array.findAll) {
		Array.prototype.findAll = function(func) {
			var items = [];
			this.forEach(function(i) { if (!ObjUtil.isFunction(i)) { if (func(i)) { items.push(i); } } });
			return items;
		};
	};

	// Returns a boolean indicating that the item exists in the array.  If a criteria function is
	// passed in it tests each item against the function.
	if (!Array.contains) {
		Array.prototype.contains = function(clause) { 
			for (var i in this) { if (this[i] == clause) { return true; } }
			return false;
		};
	};

	// Returns a new array of distinct items from the original array.
	if (!Array.distinct) {
		Array.prototype.distinct = function() { 
			var items = [];
			this.forEach(function(i) { if (!items.contains(i)) { items.push(i); } });
			return items;
		};
	};

	// Returns the minimum item in the array including strings.  If the first item is a number it
	// is assumed that the entire array is made up of numbers, likewise for strings.
	if (!Array.min) {
		Array.prototype.min = function() { 
			if (this.length > 0) {
				if (ObjUtil.isNumber(this[0])) { //Assume its an array of numbers
					return Math.min.apply(Math, this);
				} else if (ObjUtil.isString(this[0])) {
					var temp = this.copy();
					temp.sortAscending();
					return temp[0];
				}
			}
			return null;
		};
	};

	// Returns the maximum item in the array including strings.  If the first item is a number it
	// is assumed that the entire array is made up of numbers, likewise for strings.
	if (!Array.max) {
		Array.prototype.max = function() { 
			if (this.length > 0) {
				if (ObjUtil.isNumber(this[0])) { //Assume its an array of numbers
					return Math.max.apply(Math, this);
				} else {
					var temp = this.copy();
					temp.sortDescending();
					return temp[0];
				}
			}
			return null;
		};
	};

	// Returns the first item in the array.
	if (!Array.first) {
		Array.prototype.first = function() { 
			return this.length > 0 ? this[0] : null; 
		};
	};

	// Retunrs the last item in the array;
	if (!Array.last) {
		Array.prototype.last = function() { 
			return this.length > 0 ? this[this.length - 1] : null; 
		};
	};

	// Sorts the array in ascending order using the Array.compareTo method.
	if (!Array.sortAscending) {
		Array.prototype.sortAscending = function() {
			this.sort(function(a,b) { return ObjUtil.compareTo(a, b); });
		};
	};

	// Sorts the array in descending order using the Array.compareTo method.
	if (!Array.sortDescending) {
		Array.prototype.sortDescending = function() {
			this.sort(function(a,b) { return ObjUtil.compareTo(b, a); });
		};
	};

	// Sorts the array in a random order.
	if (!Array.randomize) {
		Array.prototype.randomize = function() { 
			this.sort(function() { return 0.5 - Math.random(); }); 
		};
	};

	// Returns the length of the array or a count of the items matching a criteria function.
	if (!Array.count) {
		Array.prototype.count = function(func) {
			return func == null ? this.length : this.findAll(func).length;
		};
	};

	// Returns an array containing the delta items of two arrays.
	if (!Array.except) {
		Array.prototype.except = function(arr) {
			var items = [];
			var me = this;
			this.forEach(function(i) {
				if (!ObjUtil.isFunction(i)) {
					if (!arr.contains(i)) {	items.push(i); }
				}
			});
			arr.forEach(function(i) {
				if (!ObjUtil.isFunction(i)) {
					if (!me.contains(i)) { items.push(i); }
				}
			});
			return items;
		};
	};

	// Returns an array containing the overlapping items of two arrays.
	if (!Array.intersect) {
		Array.prototype.intersect = function(arr) {
			var items = [];
			this.forEach(function(i) {
				if (!ObjUtil.isFunction(i)) {
					if (arr.contains(i)) { items.push(i); }
				}
			});
			return items;
		};
	};

	// Returns the average of the numbers in a numeric array.
	if (!Array.average) {
		Array.prototype.average = function() { 
			return this.length > 0 ? this.sum() / this.length : null; 
		};
	};

	// Returns the sum of the numbers in a numeric array.
	if (!Array.sum) {
		Array.prototype.sum = function() {
			var sum = 0;
			this.forEach(function(i) { if (ObjUtil.isNumber(i)) { sum += i; } });
			return sum;
		};
	};

	// Returns a new array combining two others but excluding duplicates.
	if (!Array.union) {
		Array.prototype.union = function(arr) {
			var items = [];
			this.forEach(function(i) { items.push(i); });
			arr.forEach(function(i) { items.push(i); });
			return items.distinct();
		};
	};

	// Adds an item to an array but only if that item doesn't already exist in the array.
	if (!Array.safePush) {
		Array.prototype.safePush = function(value) {
			if (!this.contains(value)) { this.push(value); }
		};
	};

	// Returns a random item from the array.
	if (!Array.getRandom) {
		Array.prototype.getRandom = function() {
			return this[Math.floor(Math.random() * this.length)];
		};
	};
	
	// Inserts an item into the array at a specific index.
	if (!Array.insertAt) {
		Array.prototype.insertAt = function(pos, val) {
			if (pos > -1 && pos < this.length) { this.splice(pos, 0, val); }
			return false;
		};
	};
	
	// Removes all items from the array that match the supplied criteria function.
	if (!Array.remove) {
		Array.prototype.remove = function(func) {
			for (var i in this) {
				if (func(this[i])) {
					var b = this.slice(0, i);
					var e = this.slice(i + 1, this.length);
					this.length = 0;
					this.push.apply(this, b);
					this.push.apply(this, e);
				}
			}
		};
	};

	// Returns the index of a specific element in an array.

	if (!Array.prototype.indexOf) {  
    Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {  
        "use strict";  
        if (this == null) {  
            throw new TypeError();  
        }  
        var t = Object(this);  
        var len = t.length >>> 0;  
        if (len === 0) {  
            return -1;  
        }  
        var n = 0;  
        if (arguments.length > 0) {  
            n = Number(arguments[1]);  
            if (n != n) { // shortcut for verifying if it's NaN  
                n = 0;  
            } else if (n != 0 && n != Infinity && n != -Infinity) {  
                n = (n > 0 || -1) * Math.floor(Math.abs(n));  
            }  
        }  
        if (n >= len) {  
            return -1;  
        }  
        var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);  
        for (; k < len; k++) {  
            if (k in t && t[k] === searchElement) {  
                return k;  
            }  
        }  
        return -1;  
    }  
};


    Array.prototype.clean = function(deleteValue){
        for (var i = 0; i < this.length; i++) {
            if (this[i] == deleteValue) {
                this.splice(i, 1);
                i--;
            }
        }
        return this;
    };
    
    Array.prototype.swap = function(x, y){
        var b = this[x];
        this[x] = this[y];
        this[y] = b;
        return this;
    };
})();



Inflector = {
  Inflections: {
    plural: [
    [/(quiz)$/i,               "$1zes"  ],
    [/^(ox)$/i,                "$1en"   ],
    [/([m|l])ouse$/i,          "$1ice"  ],
    [/(matr|vert|ind)ix|ex$/i, "$1ices" ],
    [/(x|ch|ss|sh)$/i,         "$1es"   ],
    [/([^aeiouy]|qu)y$/i,      "$1ies"  ],
    [/(hive)$/i,               "$1s"    ],
    [/(?:([^f])fe|([lr])f)$/i, "$1$2ves"],
    [/sis$/i,                  "ses"    ],
    [/([ti])um$/i,             "$1a"    ],
    [/(buffal|tomat)o$/i,      "$1oes"  ],
    [/(bu)s$/i,                "$1ses"  ],
    [/(alias|status)$/i,       "$1es"   ],
    [/(octop|vir)us$/i,        "$1i"    ],
    [/(ax|test)is$/i,          "$1es"   ],
    [/s$/i,                    "s"      ],
    [/$/,                      "s"      ]
    ],
    singular: [
    [/(quiz)zes$/i,                                                    "$1"     ],
    [/(matr)ices$/i,                                                   "$1ix"   ],
    [/(vert|ind)ices$/i,                                               "$1ex"   ],
    [/^(ox)en/i,                                                       "$1"     ],
    [/(alias|status)es$/i,                                             "$1"     ],
    [/(octop|vir)i$/i,                                                 "$1us"   ],
    [/(cris|ax|test)es$/i,                                             "$1is"   ],
    [/(shoe)s$/i,                                                      "$1"     ],
    [/(o)es$/i,                                                        "$1"     ],
    [/(bus)es$/i,                                                      "$1"     ],
    [/([m|l])ice$/i,                                                   "$1ouse" ],
    [/(x|ch|ss|sh)es$/i,                                               "$1"     ],
    [/(m)ovies$/i,                                                     "$1ovie" ],
    [/(s)eries$/i,                                                     "$1eries"],
    [/([^aeiouy]|qu)ies$/i,                                            "$1y"    ],
    [/([lr])ves$/i,                                                    "$1f"    ],
    [/(tive)s$/i,                                                      "$1"     ],
    [/(hive)s$/i,                                                      "$1"     ],
    [/([^f])ves$/i,                                                    "$1fe"   ],
    [/(^analy)ses$/i,                                                  "$1sis"  ],
    [/((a)naly|(b)a|(d)iagno|(p)arenthe|(p)rogno|(s)ynop|(t)he)ses$/i, "$1$2sis"],
    [/([ti])a$/i,                                                      "$1um"   ],
    [/(n)ews$/i,                                                       "$1ews"  ],
    [/s$/i,                                                            ""       ]
    ],
    irregular: [
    ['move',   'moves'   ],
    ['sex',    'sexes'   ],
    ['child',  'children'],
    ['man',    'men'     ],
    ['person', 'people'  ]
    ],
    uncountable: [
    "sheep",
    "fish",
    "series",
    "species",
    "money",
    "rice",
    "information",
    "equipment"
    ]
  },
  ordinalize: function(number) {
    if (11 <= parseInt(number) % 100 && parseInt(number) % 100 <= 13) {
      return number + "th";
    } else {
      switch (parseInt(number) % 10) {
        case  1: return number + "st";
        case  2: return number + "nd";
        case  3: return number + "rd";
        default: return number + "th";
      }
    }
  },
  pluralize: function(word) {
    for (var i = 0; i < Inflector.Inflections.uncountable.length; i++) {
      var uncountable = Inflector.Inflections.uncountable[i];
      if (word.toLowerCase == uncountable) {
        return uncountable;
      }
    }
    for (var i = 0; i < Inflector.Inflections.irregular.length; i++) {
      var singular = Inflector.Inflections.irregular[i][0];
      var plural   = Inflector.Inflections.irregular[i][1];
      if ((word.toLowerCase == singular) || (word == plural)) {
        return plural;
      }
    }
    for (var i = 0; i < Inflector.Inflections.plural.length; i++) {
      var regex          = Inflector.Inflections.plural[i][0];
      var replace_string = Inflector.Inflections.plural[i][1];
      if (regex.test(word)) {
        return word.replace(regex, replace_string);
      }
    }
  },
  singularize: function(word) {
    for (var i = 0; i < Inflector.Inflections.uncountable.length; i++) {
      var uncountable = Inflector.Inflections.uncountable[i];
      if (word.toLowerCase == uncountable) {
        return uncountable;
      }
    }
    for (var i = 0; i < Inflector.Inflections.irregular.length; i++) {
      var singular = Inflector.Inflections.irregular[i][0];
      var plural   = Inflector.Inflections.irregular[i][1];
      if ((word.toLowerCase == singular) || (word == plural)) {
        return plural;
      }
    }
    for (var i = 0; i < Inflector.Inflections.singular.length; i++) {
      var regex          = Inflector.Inflections.singular[i][0];
      var replace_string = Inflector.Inflections.singular[i][1];
      if (regex.test(word)) {
        return word.replace(regex, replace_string);
      }
    }
  }
};

function ordinalize(number) {
  return Inflector.ordinalize(number);
};

js.extend(String.prototype, {
  pluralize: function(count, plural) {
    if (typeof count == 'undefined') {
      return Inflector.pluralize(this);
    } else {
      return count + ' ' + (1 == parseInt(count) ? this : plural || Inflector.pluralize(this));
    }
  },
  singularize: function(count) {
    if (typeof count == 'undefined') {
      return Inflector.singularize(this);
    } else {
      return count + " " + Inflector.singularize(this);
    }
  }
});


window.location.querystring = (function(){

    // by Chris O'Brien, prettycode.org
    
    var collection = {};
    
    // Gets the query string, starts with '?'
    
    var querystring = window.location.search;
    
    // Empty if no query string
    
    if (!querystring) {
        return {
            toString: function(){
                return "";
            }
        };
    }
    
    // Decode query string and remove '?'
    
    querystring = decodeURI(querystring.substring(1));
    
    // Load the key/values of the return collection
    
    var pairs = querystring.split("&");
    
    for (var i = 0; i < pairs.length; i++) {
    
        // Empty pair (e.g. ?key=val&&key2=val2)
        
        if (!pairs[i]) {
            continue;
        }
        
        // Don't use split("=") in case value has "=" in it
        
        var seperatorPosition = pairs[i].indexOf("=");
        
        if (seperatorPosition == -1) {
            collection[pairs[i]] = "";
        }
        else {
            collection[pairs[i].substring(0, seperatorPosition)] = pairs[i].substr(seperatorPosition + 1);
        }
    }
    
    // toString() returns the key/value pairs concatenated
    
    collection.toString = function(){
        return ("?" + querystring);
    };
    
    return collection;
})();



window.onerror = function(message, url, linenumber) {
  alert("JavaScript error: " + message + " on line " + linenumber + " for " + url);
}





